/* Copyright 2007-2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 * Marcus Cuda
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this 
 *   list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the dnAnalytics Project nor the names of its contributors
 *   may be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using dnAnalytics.Math;
using dnAnalytics.Properties;

namespace dnAnalytics.LinearAlgebra
{
    /// <summary>
    /// A vector using dense storage.
    /// </summary>
    [Serializable]
    public partial class DenseVector : Vector
    {
        private readonly double[] mData;

        /// <summary> 
        /// Constructs a vector with the given size.
        /// </summary> 
        /// <param name="size">the size of the vector.</param> 
        /// <exception cref="ArgumentException">If <paramref name="size"/> is less than one.</exception>
        public DenseVector(int size)
            : base(size)
        {
            mData = new double[size];
        }

        /// <summary> 
        /// Constructs a vector with the given size and with each element set the given value. 
        /// </summary> 
        /// <param name="size">the size of the vector.</param> 
        /// <param name="value">the value to set each element to.</param> 
        /// <exception cref="ArgumentException">If <paramref name="size"/> is less than one.</exception>
        public DenseVector(int size, double value) : this(size)
        {
            for (int i = 0; i < mData.Length; i++)
            {
                mData[i] = value;
            }
        }

        /// <summary> 
        /// Constructs a vector from the given array.
        /// </summary> 
        /// <param name="array">the array to copy the values from.</param> 
        /// <exception cref="NullReferenceException">If <paramref name="array"/> is <see langword="null" />.</exception>
        public DenseVector(double[] array)
            : this(array.Length)
        {
            Buffer.BlockCopy(array, 0, mData, 0, array.Length*Constants.SizeOfDouble);
        }

        /// <summary> 
        /// Constructs a vector from the given list.
        /// </summary> 
        /// <param name="list">the list to copy the values from.</param> 
        /// <exception cref="NullReferenceException">If <paramref name="list"/> is <see langword="null" />.</exception>
        public DenseVector(IList<double> list): this(list.Count)
        {
            for (int i = 0; i < mData.Length; i++)
            {
                mData[i] = list[i];
            }
        }
        
        /// <summary>
        /// Creates a new vector from another vector.
        /// </summary>
        /// <param name="other">The matrix to create the new matrix from.</param>
        public DenseVector(Vector other) : this(other.Count)
        {
            DenseVector vector = other as DenseVector;
            if (vector == null)
            {
                //using enumerators since they will be more efficient for copying sparse matrices
                foreach (KeyValuePair<int, double> item in other.GetIndexedEnumerator())
                {
                    mData[item.Key] = item.Value;
                }
            }
            else
            {
                Buffer.BlockCopy(vector.mData, 0, mData, 0, mData.Length*Constants.SizeOfDouble);
            }
        }

        internal double[] Data
        {
            get { return mData; }
        }

        /// <summary>Gets or sets the value at the given <paramref name="index"/>.</summary>
        /// <param name="index">The index of the value to get or set.</param>
        /// <returns>The value of the vector at the given <paramref name="index"/>.</returns> 
        /// <exception cref="IndexOutOfRangeException">If <paramref name="index"/> is negative or 
        /// greater than the size of the vector.</exception>
        public override double this[int index]
        {
            get { return mData[index]; }
            set { mData[index] = value; }
        }

        /// <summary>
        /// Creates a <strong>Vector</strong> of the given size;
        /// </summary>
        /// <param name="size">The size of the <strong>Vector</strong> to create.</param>
        /// <returns>The new <strong>Vector</strong>.</returns>
        protected internal override Vector CreateVector(int size)
        {
            return new DenseVector(size);
        }

        /// <summary>
        /// Creates a matrix with the given dimensions using the same storage type
        /// as this vector uses.
        /// </summary>
        /// <param name="rows">The number of rows.</param>
        /// <param name="columns">The number of columns.</param>
        /// <returns>A matrix with the given dimensions.</returns>
        public override Matrix CreateMatrix(int rows, int columns)
        {
            return new DenseMatrix(rows, columns);
        }

        /// <summary>
        /// Sets the each element to zero.
        /// </summary>
        public override void Clear()
        {
            Array.Clear(mData, 0, mData.Length);
        }


        /// <summary>
        /// Copies the values of this vector into the target vector.
        /// </summary>
        /// <param name="target">The vector to copy elements into.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="target"/> is <see langword="null" />.</exception>
        /// <exception cref="NotConformableException">If <paramref name="target"/> is not the same size as this vector.</exception>
        public override void CopyTo(Vector target)
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }
            if (Count != target.Count)
            {
                throw new NotConformableException("target", Resources.ParameterNotConformable);
            }

            DenseVector otherVector = target as DenseVector;
            if (otherVector == null)
            {
                for (int i = 0; i < mData.Length; i++)
                {
                    target[i] = mData[i];
                }
            }
            else
            {
                Buffer.BlockCopy(mData, 0, otherVector.mData, 0, mData.Length*Constants.SizeOfDouble);
            }
        }


        /// <summary>
        /// Creates a vector containing specified elements.
        /// </summary>
        /// <param name="index">The first element to begin copying from.</param>
        /// <param name="length">The number of elements to copy.</param>
        /// <returns>A vector containing a copy of the specified elements.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><list><item>If <paramref name="index"/> is not positive or
        /// greater than or equal to the size of the vector.</item>
        /// <item>If <paramref name="index"/> + <paramref name="length"/> is greater than or equal to the size of the vector.</item>
        /// </list></exception>
        /// <exception cref="ArgumentException">If <paramref name="length"/> is not positive.</exception>
        public override Vector GetSubVector(int index, int length)
        {
            if (index < 0 || index >= Count)
            {
                throw new ArgumentOutOfRangeException("index");
            }
            if (index + length > Count)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            DenseVector ret = new DenseVector(length);
            Buffer.BlockCopy(mData, index*Constants.SizeOfDouble, ret.mData, 0, length*Constants.SizeOfDouble);
            return ret;
        }

        /// <summary>
        /// Adds a scalar to each element of the vector.
        /// </summary>
        /// <param name="scalar">The scalar to add.</param>
        public override void Add(double scalar)
        {
            if (scalar == 0.0)
            {
                return;
            }

            for (int i = 0; i < mData.Length; i++)
            {
                mData[i] += scalar;
            }
        }

        /// <summary>
        /// Subtracts a scalar from each element of the vector.
        /// </summary>
        /// <param name="scalar">The scalar to subtract.</param>
        public override void Subtract(double scalar)
        {
            if (scalar == 0.0)
            {
                return;
            }

            for (int i = 0; i < mData.Length; i++)
            {
                mData[i] -= scalar;
            }
        }


        /// <summary>
        /// Computes the sum of the vector's elements.
        /// </summary>
        /// <returns>The sum of the vector's elements.</returns>
        public override double Sum()
        {
            double ret = 0;
            for (int i = 0; i < mData.Length; i++)
            {
                ret += mData[i];
            }
            return ret;
        }

        /// <summary>
        /// Returns the data contained in the vector as an array.
        /// </summary>
        /// <returns>The vector's data as an array.</returns>        
        public override double[] ToArray()
        {
            double[] ret = new double[Count];
            Buffer.BlockCopy(mData, 0, ret, 0, mData.Length*Constants.SizeOfDouble);
            return ret;
        }

        /// <summary>
        /// Set the values of this vector to the given values.
        /// </summary>
        /// <param name="values">The array containing the values to use.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="values"/> is <see langword="null" />.</exception>
        /// <exception cref="NotConformableException">If <paramref name="values"/> is not the same size as this vector.</exception>
        public override void SetValues(double[] values)
        {
            if (values == null)
            {
                throw new ArgumentNullException("values");
            }
            if (values.Length != Count)
            {
                throw new NotConformableException("values", Resources.ArrayParameterNotConformable);
            }

            Buffer.BlockCopy(values, 0, mData, 0, mData.Length*Constants.SizeOfDouble);
        }

        /// <summary>
        /// Returns a <strong>Vector</strong> containing the same values of rightSide. 
        /// </summary>
        /// <remarks>This method is included for completeness.</remarks>
        /// <param name="rightSide">The vector to get the values from.</param>
        /// <returns>A vector containing a the same values as <paramref name="rightSide"/>.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static DenseVector operator +(DenseVector rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }
            return new DenseVector(rightSide);
        }

        /// <summary>
        /// Adds two <strong>Vectors</strong> together and returns the results.
        /// </summary>
        /// <param name="leftSide">One of the vectors to add.</param>
        /// <param name="rightSide">One of the vectors to add.</param>
        /// <returns>The result of the addition.</returns>
        /// <exception cref="NotConformableException">If <paramref name="leftSide"/> and <paramref name="rightSide"/> are not the same size.</exception>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> or <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static DenseVector operator +(DenseVector leftSide, DenseVector rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }

            if (leftSide.Count != rightSide.Count)
            {
                throw new NotConformableException(Resources.ParametersNotConformable);
            }

            DenseVector ret = new DenseVector(leftSide);
            ret.Add(rightSide);
            return ret;
        }

        /// <summary>
        /// Negates each element of the vector.
        /// </summary>
        /// <param name="rightSide">The vector to negate.</param>
        /// <returns>A vector containing the negated values.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static DenseVector operator -(DenseVector rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }

            DenseVector ret = new DenseVector(rightSide);
            ret.Negate();
            return ret;
        }

        /// <summary>
        /// Subtracts two vectors and returns the results.
        /// </summary>
        /// <param name="leftSide">The vector to subtract from.</param>
        /// <param name="rightSide">The vector to subtract.</param>
        /// <returns>The result of the subtraction.</returns>
        /// <exception cref="NotConformableException">If <paramref name="leftSide"/> and <paramref name="rightSide"/> are not the same size.</exception>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> or <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static DenseVector operator -(DenseVector leftSide, DenseVector rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }

            if (leftSide.Count != rightSide.Count)
            {
                throw new NotConformableException(Resources.ParametersNotConformable);
            }
            DenseVector ret = new DenseVector(leftSide);
            ret.Subtract(rightSide);
            return ret;
        }

        /// <summary>
        /// Divides a <strong>Vector</strong> by a constant and returns the result.
        /// </summary>
        /// <param name="leftSide">The vector to divide.</param>
        /// <param name="rightSide">The constant to divide the vector by.</param>
        /// <returns>The result of the division.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> is <see langword="null" />.</exception>
        public static DenseVector operator /(DenseVector leftSide, double rightSide)
        {
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }

            DenseVector ret = new DenseVector(leftSide);
            ret.Divide(rightSide);
            return ret;
        }

        /// <summary>
        /// Multiplies a <strong>Vector</strong> by a constant and returns the result.
        /// </summary>
        /// <param name="leftSide">The vector to multiply.</param>
        /// <param name="rightSide">The constant to multiply the vector by.</param>
        /// <returns>The result of the multiplication.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static DenseVector operator *(double leftSide, DenseVector rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }
            DenseVector ret = new DenseVector(rightSide);
            ret.Multiply(leftSide);
            return ret;
        }

        /// <summary>
        /// Multiplies a <strong>Vector</strong> by a constant and returns the result.
        /// </summary>
        /// <param name="leftSide">The vector to multiply.</param>
        /// <param name="rightSide">The constant to multiply the vector by.</param>
        /// <returns>The result of the multiplication.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> is <see langword="null" />.</exception>
        public static DenseVector operator *(DenseVector leftSide, double rightSide)
        {
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }
            DenseVector ret = new DenseVector(leftSide);
            ret.Multiply(rightSide);
            return ret;
        }

        /// <summary>
        /// Multiples two <strong>Vectors</strong>. The first vector is treated as column vector and
        /// the second as row vector.
        /// </summary>
        /// <param name="leftSide">One of the vectors to multiply.</param>
        /// <param name="rightSide">One of the vectors to multiply.</param>
        /// <returns>The result of multiplication.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> or <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static DenseMatrix operator *(DenseVector leftSide, DenseVector rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }

            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }
            return (DenseMatrix)leftSide.Multiply(rightSide);
        }

        /// <summary>
        /// Copies the requested elements from this vector to another.
        /// </summary>
        /// <param name="destination">The vector to copy the elements to.</param>
        /// <param name="offset">The element to start copying from.</param>
        /// <param name="destinationOffset">The element to start copying to.</param>
        /// <param name="count">The number of elements to copy.</param>
        public override void CopyTo(Vector destination, int offset, int destinationOffset, int count)
        {
            if (destination == null)
            {
                throw new ArgumentNullException("destination");
            }
            if (offset >= mData.Length)
            {
                throw new ArgumentOutOfRangeException("offset");
            }
            if (offset + count > mData.Length)
            {
                throw new ArgumentOutOfRangeException("count");
            }

            if (destinationOffset >= destination.Count)
            {
                throw new ArgumentOutOfRangeException("destinationOffset");
            }

            if (destinationOffset + count > destination.Count)
            {
                throw new ArgumentOutOfRangeException("count");
            }

            DenseVector other = destination as DenseVector;
            if (other == null)
            {
                base.CopyTo(destination, offset, destinationOffset, count);
            }
            else
            {
                Buffer.BlockCopy(mData, offset * Constants.SizeOfDouble, other.mData, destinationOffset * Constants.SizeOfDouble, count * Constants.SizeOfDouble);
            }
        }
    }
}
